// config file module, added by regret

/* changelog:
 update 5: add config class
 update 4: change format, improve compatibility
 update 3: improve xml loading
 update 2: add inline functions
 update 1: create
*/

#include "config.h"
#include "burner.h"
#include "maphkeys.h"
#include "vid_filter.h"
#include <locale.h>

const float Config::configVersion = 0.06f;
bool Config::saveEnabled = true;
int Config::cfgVersion = 0;

Config::Config()
{
}

Config::~Config()
{
}

// inline functions
inline void Config::addTextNode(ticpp::Element& parent, const char* title, const char* value)
{
	if (!value) {
		return;
	}

	ticpp::Text myText(value);
	if (!title) {
		parent.SetText(myText);
	} else {
		ticpp::Element myTitle(title);
		myTitle.LinkEndChild(&myText);
		parent.LinkEndChild(&myTitle);
	}
}

template <typename T>
inline void Config::setAttr(ticpp::Element& element, const char* attr, const T& value)
{
	if (!attr) {
		return;
	}
	element.SetAttribute(attr, value);
}

inline ticpp::Element* Config::findElement(ticpp::Element* element, const char* attr)
{
	if (!element || !attr) {
		return NULL;
	}
	return element->FirstChildElement(attr, false);
}

template <typename T>
inline void Config::getAttr(ticpp::Element* element, const char* attr, T* value)
{
	if (!element || !attr || !value) {
		return;
	}
	element->GetAttribute(attr, value, false);
}

inline void Config::getTextAttr(ticpp::Element* element, const char* attr, wchar_t* name)
{
	if (!element || !attr || !name) {
		return;
	}

	string str = "";
	element->GetAttribute(attr, &str, false);
	wcscpy(name, U8toW(str.c_str()));
}

inline void Config::getTextStr(ticpp::Element* element, wchar_t* name)
{
	if (!element || !name) {
		return;
	}

	string str = "";
	element->GetText(&str, false);

	if (str != "") {
		wcscpy(name, U8toW(str.c_str()));
	}
}

// get config file version
float Config::version()
{
	return configVersion;
}

void Config::setSaveEnable(bool enable)
{
	saveEnabled = enable;
}

// get config filename
void Config::createConfigName(char* config)
{
	sprintf(config, "%s.xml", WtoA(appExeName));
}

// Read in the config file for the whole application
bool Config::load()
{
	setlocale(LC_ALL, "");

	char configName[MAX_PATH];
	createConfigName(configName);

	if (!fileExistsA(configName)) {
		return false;
	}

	try
	{
		string str = "";
		ticpp::Document doc(configName);
	    doc.LoadFile();

		ticpp::Element* root = doc.FirstChildElement();
		float xmlVersion;
		root->GetAttribute("version", &xmlVersion, false);
		if (xmlVersion < configVersion) {
			// TODO: don't read config ?
		}

		ticpp::Element* element, *parent, *child; //

		element = findElement(root, "version");
		element->GetText(&str);
		cfgVersion = strtol(str.c_str(), NULL, 0);

		// emulation
		element = findElement(root, "emulation");
		getAttr(element, "all-ram", &drvSaveAll);

		// video
		element = findElement(root, "video");
		child = findElement(element, "fullscreen");
		getAttr(child, "width", &vidWidth);
		getAttr(child, "height", &vidHeight);
		getAttr(child, "depth", &vidDepth);
		getAttr(child, "refresh", &vidRefresh);

		child = findElement(element, "adjust");
		getAttr(child, "rotate-vertical", &vidRotationAdjust);

		child = findElement(element, "screen-size");
		getAttr(child, "window", &scrnWindowSize);

		child = findElement(element, "window-position");
		getAttr(child, "x", &windowPosX);
		getAttr(child, "y", &windowPosY);

		child = findElement(element, "stretch");
		getAttr(child, "full-stretch", &vidFullStretch);
		getAttr(child, "correct-aspect", &vidCorrectAspect);

		child = findElement(element, "color");
		getAttr(child, "enable", &colorAdjustEnable);
		getAttr(child, "contrast", &color_contrast);
		getAttr(child, "brightness", &color_brightness);
		getAttr(child, "gamma", &color_gamma);
		getAttr(child, "grayscale", &color_grayscale);
		getAttr(child, "invert", &color_invert);
		getAttr(child, "sepia", &color_sepia);

		child = findElement(element, "vsync");
		getAttr(child, "enable", &vidVSync);
		child = findElement(element, "triple-buffer");
		getAttr(child, "enable", &vidTripleBuffer);

		// render
		parent = findElement(element, "render");
		if (parent) {
			child = findElement(parent, "mode");
			getTextStr(child, vidSelect);

			child = findElement(parent, "render-driver");
			getAttr(child, "adapter", &vidAdapter);
			child = findElement(parent, "filter");
			getAttr(child, "linear", &filter.linear);
			getAttr(child, "use-pixelfilter", &filter.enable);
			getAttr(child, "pixel-filter", &filter.filterId);
			getAttr(child, "scanline-intensity", &scanlineFilter.intensity);
		}

		// video others
		child = findElement(element, "monitor");
		getAttr(child, "auto-aspect", &vidAutoScrnAspect);
		getAttr(child, "aspect-x", &vidScrnAspectX);
		getAttr(child, "aspect-y", &vidScrnAspectY);

		child = findElement(element, "frame");
		getAttr(child, "auto-frameskip", &autoFrameSkip);
		getAttr(child, "force-60hz", &burnForce60Hz);

		// audio
		element = findElement(root, "audio");
		if (element) {
			child = findElement(element, "sound");
			getTextStr(child, audSelect);
			child = findElement(element, "device");
			getAttr(child, "ds", &dsDevice);
			getAttr(child, "xa2", &xa2Device);
			getAttr(child, "oal", &oalDevice);
			child = findElement(element, "setting");
			getAttr(child, "rate", &audSampleRate);
			getAttr(child, "frame", &audSegCount);
			getAttr(child, "dsp", &audDSPModule);
			getAttr(child, "pcm-interp", &pcmInterpolation);
			getAttr(child, "fm-interp", &fmInterpolation);
			getAttr(child, "stereo-upmix", &audStereoUpmixing);
		}

		// gui
		element = findElement(root, "gui");
		if (element) {
			child = findElement(element, "language");
			getTextStr(child, szLanguage);
			child = findElement(element, "gamelist");
			getTextStr(child, transGamelistFile);

			child = findElement(element, "gui-misc");
			getAttr(child, "on-top", &showOnTop);
			getAttr(child, "auto-fullscreen", &fullscreenOnStart);
			getAttr(child, "menu-style", &menuNewStyle);
			getAttr(child, "nosplash", &disableSplash);
			getAttr(child, "splash-time", &splashTime);

			child = findElement(element, "gamelist-dlg");
			getAttr(child, "options", &loadMenuShowX);
			getAttr(child, "drivers", &loadDriverShowX);
			getAttr(child, "sys-sel", &loadSystemSel);
			getAttr(child, "tab-sel", &loadTabSel);
			child = findElement(child, "user-filter");
			getTextStr(child, userFilterStr);

			child = findElement(element, "ips");
			getAttr(child, "language", &patcher.patchLang);
			getAttr(child, "dependancy", &patcher.enforceDep);

			child = findElement(element, "skin");
			getAttr(child, "use-placeholder", &vidUsePlaceholder);
			getAttr(child, "random", &randomSkin);
			getTextStr(child, skinPlaceHolder);
		}

		// preferences
		element = findElement(root, "preferences");
		if (element) {
			child = findElement(element, "settings");
			getAttr(child, "always-processkey", &alwaysProcessKey);
			getAttr(child, "auto-pause", &autoPause);
			getAttr(child, "avi-audio", &aviIntAudio);
			getAttr(child, "use-gdip", &useGdip);

			child = findElement(element, "fastforward");
			getAttr(child, "speed", &fastSpeed);
			child = findElement(element, "thread");
			getAttr(child, "priority", &appThreadPriority);
			child = findElement(element, "autofire");
			getAttr(child, "enable", &autofire.enabled);
			getAttr(child, "default-delay", &autofire.defaultDelay);
			child = findElement(element, "misc");
			getAttr(child, "effect", &showEffect);

			child = findElement(element, "controls");
			if (child) {
				child = child->FirstChildElement();
				for (int i = 0; i < 4, child; i++, child = child->NextSiblingElement(false)) {
					getTextStr(child, playerDefaultIni[i]);
				}
			}
		}

		// paths
		element = findElement(root, "paths");
		if (element) {
			child = findElement(element, "rom");
			if (child) {
				child = child->FirstChildElement();
				for (int i = 0; i < DIRS_MAX, child;
					i++, child = child->NextSiblingElement(false)) {
					getTextStr(child, appRomPaths[i]);
				}
			}

			child = findElement(element, "misc");
			if (child) {
				child = child->FirstChildElement();
				for (int i = PATH_PREVIEW; i < PATH_SUM, child;
					i++, child = child->NextSiblingElement(false)) {
					getTextStr(child, miscPaths[i]);
				}
			}
		}

		// hotkeys
		int keymode = 0;
		element = findElement(root, "hotkeys");
		if (element) {
			for (int i = 0; !lastCustomKey(customKeys[i]); i++) {
				CustomKey& customkey = customKeys[i];

				child = findElement(element, customkey.config_code);
				if (!child) {
					continue;
				}
				getAttr(child, "key", &customkey.key);
				getAttr(child, "mod", &customkey.keymod);
			}
		}
	}
	catch (ticpp::Exception& ex)
	{
		return false;
	}

	return true;
}

// Write out the config file for the whole application
bool Config::save()
{
	if (!saveEnabled)
		return true;

	char configName[MAX_PATH];
	createConfigName(configName);

	try {
		char tempStr[64] = "";

		// root
		ticpp::Document doc;
		ticpp::Declaration decl("1.0", "UTF-8", "");
		doc.LinkEndChild(&decl);

		ticpp::Element root("configuration");
		setAttr(root, "version", configVersion);
		ticpp::Comment comment("Don't edit this file manually unless you know what you're doing\n" \
				"The emulator will restore default settings when this file is deleted");
		doc.LinkEndChild(&comment);
		doc.LinkEndChild(&root);

		// title
		sprintf(tempStr, "0x%06X", burnVersion);
		addTextNode(root, "version", tempStr);

		// emulation
		ticpp::Element emulation("emulation");
		root.LinkEndChild(&emulation);
		setAttr(emulation, "all-ram", drvSaveAll);

		// video
		ticpp::Element video("video");
		root.LinkEndChild(&video);

		ticpp::Element fullscreen("fullscreen");
		video.LinkEndChild(&fullscreen);
		setAttr(fullscreen, "width", vidWidth);
		setAttr(fullscreen, "height", vidHeight);
		setAttr(fullscreen, "depth", vidDepth);
		setAttr(fullscreen, "refresh", vidRefresh);

		ticpp::Element adjust("adjust");
		video.LinkEndChild(&adjust);
		setAttr(adjust, "rotate-vertical", vidRotationAdjust);

		ticpp::Element screen_size("screen-size");
		video.LinkEndChild(&screen_size);
		setAttr(screen_size, "window", scrnWindowSize);

		ticpp::Element window_position("window-position");
		video.LinkEndChild(&window_position);
		setAttr(window_position, "x", windowPosX);
		setAttr(window_position, "y", windowPosY);

		ticpp::Element stretch("stretch");
		video.LinkEndChild(&stretch);
		setAttr(stretch, "full-stretch", vidFullStretch);
		setAttr(stretch, "correct-aspect", vidCorrectAspect);

		ticpp::Element color("color");
		video.LinkEndChild(&color);
		setAttr(color, "enable", colorAdjustEnable);
		setAttr(color, "contrast", color_contrast);
		setAttr(color, "brightness", color_brightness);
		setAttr(color, "gamma", color_gamma);
		setAttr(color, "grayscale", color_grayscale);
		setAttr(color, "invert", color_invert);
		setAttr(color, "sepia", color_sepia);

		ticpp::Element vsync("vsync");
		video.LinkEndChild(&vsync);
		setAttr(vsync, "enable", vidVSync);

		ticpp::Element triple_buffer("triple-buffer");
		video.LinkEndChild(&triple_buffer);
		setAttr(triple_buffer, "enable", vidTripleBuffer);

		// video render
		ticpp::Element render("render");
		video.LinkEndChild(&render);
		addTextNode(render, "mode", WtoU8(vidSelect));

		ticpp::Element render_driver("render-driver");
		render.LinkEndChild(&render_driver);
		setAttr(render_driver, "adapter", vidAdapter);

		ticpp::Element _filter("filter");
		render.LinkEndChild(&_filter);
		setAttr(_filter, "linear", filter.linear);
		setAttr(_filter, "use-pixelfilter", filter.enable);
		setAttr(_filter, "pixel-filter", filter.filterId);
		setAttr(_filter, "scanline-intensity", scanlineFilter.intensity);

		// video others
		ticpp::Element monitor("monitor");
		video.LinkEndChild(&monitor);
		setAttr(monitor, "auto-aspect", vidAutoScrnAspect);
		setAttr(monitor, "aspect-x", vidScrnAspectX);
		setAttr(monitor, "aspect-y", vidScrnAspectY);

		ticpp::Element frame("frame");
		video.LinkEndChild(&frame);
		setAttr(frame, "auto-frameskip", autoFrameSkip);
		setAttr(frame, "force-60hz", burnForce60Hz);

		// audio
		ticpp::Element audio("audio");
		root.LinkEndChild(&audio);
		addTextNode(audio, "sound", WtoU8(audSelect));

		ticpp::Element device("device");
		audio.LinkEndChild(&device);
		setAttr(device, "ds", dsDevice);
		setAttr(device, "xa2", xa2Device);
		setAttr(device, "oal", oalDevice);

		ticpp::Element audio_set("setting");
		audio.LinkEndChild(&audio_set);
		setAttr(audio_set, "rate", audSampleRate);
		setAttr(audio_set, "frame", audSegCount);
		setAttr(audio_set, "dsp", audDSPModule);
		setAttr(audio_set, "pcm-interp", pcmInterpolation);
		setAttr(audio_set, "fm-interp", fmInterpolation);
		setAttr(audio_set, "stereo-upmix", audStereoUpmixing);

		// gui
		ticpp::Element gui("gui");
		root.LinkEndChild(&gui);
		addTextNode(gui, "language", WtoU8(szLanguage));
		addTextNode(gui, "gamelist", WtoU8(transGamelistFile));

		ticpp::Element gui_misc("gui-misc");
		gui.LinkEndChild(&gui_misc);
		setAttr(gui_misc, "on-top", showOnTop);
		setAttr(gui_misc, "auto-fullscreen", fullscreenOnStart);
		setAttr(gui_misc, "menu-style", menuNewStyle);
		setAttr(gui_misc, "nosplash", disableSplash);
		setAttr(gui_misc, "splash-time", splashTime);

		// gui load game dialog
		ticpp::Element gamelist("gamelist-dlg");
		gui.LinkEndChild(&gamelist);
		setAttr(gamelist, "options", loadMenuShowX);
		setAttr(gamelist, "drivers", loadDriverShowX);
		setAttr(gamelist, "sys-sel", loadSystemSel);
		setAttr(gamelist, "tab-sel", loadTabSel);
		addTextNode(gamelist, "user-filter", WtoU8(userFilterStr));

		// gui ips
		ticpp::Element ips("ips");
		gui.LinkEndChild(&ips);
		setAttr(ips, "language", patcher.patchLang);
		setAttr(ips, "dependancy", patcher.enforceDep);

		ticpp::Element skin("skin");
		gui.LinkEndChild(&skin);
		setAttr(skin, "use-placeholder", vidUsePlaceholder);
		setAttr(skin, "random", randomSkin);
		addTextNode(skin, NULL, WtoU8(skinPlaceHolder));

		// preferences
		ticpp::Element preference("preferences");
		root.LinkEndChild(&preference);

		ticpp::Element settings("settings");
		preference.LinkEndChild(&settings);
		setAttr(settings, "always-processkey", alwaysProcessKey);
		setAttr(settings, "auto-pause", autoPause);
		setAttr(settings, "avi-audio", aviIntAudio);
		setAttr(settings, "use-gdip", useGdip);
		ticpp::Element fastforward("fastforward");
		preference.LinkEndChild(&fastforward);
		setAttr(fastforward, "speed", fastSpeed);
		ticpp::Element thread("thread");
		preference.LinkEndChild(&thread);
		setAttr(thread, "priority", appThreadPriority);
		ticpp::Element _autofire("autofire");
		preference.LinkEndChild(&_autofire);
		setAttr(_autofire, "enable", autofire.enabled);
		setAttr(_autofire, "default-delay", autofire.defaultDelay);

		// pref misc
		ticpp::Element pref_misc("misc");
		preference.LinkEndChild(&pref_misc);
		setAttr(pref_misc, "effect", showEffect);

		ticpp::Element controls("controls");
		preference.LinkEndChild(&controls);
		for (int i = 0; i < 4; i++) {
			sprintf(tempStr, "default%d", i);
			addTextNode(controls, tempStr, WtoU8(playerDefaultIni[i]));
		}

		// paths
		ticpp::Element paths("paths");
		root.LinkEndChild(&paths);

		ticpp::Element rom_path("rom");
		paths.LinkEndChild(&rom_path);
		for (int i = 0; i < DIRS_MAX; i++) {
			addTextNode(rom_path, "path", WtoU8(appRomPaths[i]));
		}

		ticpp::Element misc_path("misc");
		paths.LinkEndChild(&misc_path);
		for (int i = PATH_PREVIEW; i < PATH_SUM; i++) {
			sprintf(tempStr, "path%d", i);
			addTextNode(misc_path, tempStr, WtoU8(miscPaths[i]));
		}

		// hotkeys
		ticpp::Element hotkeys("hotkeys");
		root.LinkEndChild(&hotkeys);

		for (int i = 0; !lastCustomKey(customKeys[i]); i++) {
			CustomKey& customkey = customKeys[i];

			ticpp::Element key(customkey.config_code);
			hotkeys.LinkEndChild(&key);
			setAttr(key, "key", customkey.key);
			setAttr(key, "mod", customkey.keymod);
		}

		// save file
		doc.SaveFile(configName, TIXML_ENCODING_UTF8);
	}
	catch (ticpp::Exception& ex) {
		return false;
	}

	return true;
}
